package nachos.threads;
import nachos.ag.BoatGrader;
import nachos.machine.Lib;


public class Boat
{
    static BoatGrader bg;
    private static final char dbgBoat = 'b';
    protected static Communicator comm1 = new Communicator();
    
    public static void selfTest()
    {
	BoatGrader b = new BoatGrader();
	
	//System.out.println("\n ***Testing Boats with only 2 children***");
	//begin(0, 2, b);

//	System.out.println("\n ***Testing Boats with 2 children, 1 adult***");
  	//begin(1, 2, b);

  	//System.out.println("\n ***Testing Boats with 3 children, 3 adults***");
  	begin(3, 2, b);
    }

    public static void begin( int adults, int children, BoatGrader b )
    {
	// Store the externally generated autograder in a class
	// variable to be accessible by children.
	bg = b;

	// Instantiate global variables here
	
	// Create threads here. See section 3.4 of the Nachos for Java
	// Walkthrough linked from the projects page.

	/*Runnable r = new Runnable() {
	    public void run() {
                SampleItinerary();
            }
        };
        KThread t = new KThread(r);
        t.setName("Sample Boat Thread");
        t.fork();*/
        
        
        for(int i=0; i<adults; i++) {
            KThread adult = new KThread(new Adult());
            adult.setName("adult" + i);
           // oAdultsList.add(aThread);
            adult.fork();
        }
	    //start child threads
	    for(int i=0; i<children; i++) {
	            //Child cThread = new Child();
	            KThread child = new KThread(new Child());
	            child.setName("child" + i);
	           // oChildrenList.add(cThread);
	            child.fork();
	    }

    	//KThread.currentThread().yield();
	    int done = comm1.listen();
    	
    }

    static void AdultItinerary()
    {
	/* This is where you should put your solutions. Make calls
	   to the BoatGrader to show that it is synchronized. For
	   example:
	       bg.AdultRowToMolokai();
	   indicates that an adult has rowed the boat across to Molokai
	*/
    }

    static void ChildItinerary()
    {
    }

    static void SampleItinerary()
    {
	// Please note that this isn't a valid solution (you can't fit
	// all of them on the boat). Please also note that you may not
	// have a single thread calculate a solution and then just play
	// it back at the autograder -- you will be caught.
	System.out.println("\n ***Everyone piles on the boat and goes to Molokai***");
	bg.AdultRowToMolokai();
	bg.ChildRideToMolokai();
	bg.AdultRideToMolokai();
	bg.ChildRideToMolokai();
    }
    
    private static class Traveller{
    	protected static boolean boatOnOahu = true;
    	protected static int childrenOnOahu;
    	protected static int adultsOnOahu;
    	protected static int childrenOnMolokai;
    	protected static Lock counterLock = new Lock();
    	protected static Lock boatLock = new Lock();
    	protected static Lock spareKidLock = new Lock();
    	protected static Condition2 waitForAction = new Condition2(spareKidLock);
    	//protected Condition2 = new Condition2(boatLock);
    	protected static Lock myTurnLock = new Lock();
    	protected static Condition2 canTravelonOhau = new Condition2(myTurnLock);
    	protected static Condition2 canTravelOnMolokai = new Condition2(myTurnLock);
    	protected static Condition2 adultCanTravelOnOhau = new Condition2(myTurnLock);
    	protected static Condition2 childCanTravelOnOhau = new Condition2(myTurnLock);
    	protected static Condition2 kidsCanWait = new Condition2(myTurnLock);
    	protected static Condition2 rideWithPassenger = new Condition2(boatLock);
       	protected static boolean done = false;
    	protected final static String OHAU = "Ohau";
    	protected final static String MOLOKAI = "Molokai";
    	protected static String boatLocation = OHAU;
    	protected static int kidsInABoat = 0;
    	protected static Communicator comm = new Communicator();
    	protected static int adultsWaiting=0;
    	protected static int kidsWaiting = 0;
    	protected static boolean kidOnMolokai = false;
    }
    
    private static class Child extends Traveller implements Runnable{

    	public Child(){
    		
    		counterLock.acquire();
    		childrenOnOahu++;
    		counterLock.release();
    	}
		@Override
		public void run() {
			String myLocation = OHAU;
			boolean iamdone = false;
			boolean pilot = false;
			boolean withPassenger = false;
			int adultsLeft = 0;
			int kidsLeft = 0;
			do{
				Lib.debug(dbgBoat, KThread.currentThread().getName() + " wants to ride");
				myTurnLock.acquire();
				Lib.debug(dbgBoat, KThread.currentThread().getName() + " sees kids in a boat: " + kidsInABoat);
				
				if(myLocation.equals(OHAU)){
					if(kidOnMolokai && (kidsInABoat ==0)){
						
						//canTravelonOhau.wake();
						if(adultsOnOahu > 0){
							canTravelonOhau.wake();
							Lib.debug(dbgBoat, KThread.currentThread().getName() + "kidsCanWait sleeping" + adultsOnOahu);
							kidsCanWait.sleep();
						}
					}
					Lib.debug(dbgBoat, KThread.currentThread().getName() +
									" got the lock on " + myLocation);
					if(!boatLocation.equals(OHAU)){
						canTravelonOhau.sleep();
					}
					Lib.debug(dbgBoat, KThread.currentThread().getName() + 
								" sees " + kidsInABoat + " in a boat and " 
								+ childrenOnOahu + " on Ohau");
					if(kidsInABoat == 0){
						kidsInABoat++;
						if(childrenOnOahu > 1){
							//nobody in a boat yet, and there is another kid on the island
							//I am going to wait for this other kid
							
							canTravelonOhau.wake();
							
							Lib.debug(dbgBoat, KThread.currentThread().getName() + 
									" waiting on childCanTravelOnOhau");
							
							childCanTravelOnOhau.sleep();
							Lib.debug(dbgBoat, KThread.currentThread().getName() + " passenger wakes up");
						}else{
							
							pilot = true;
						}
					}else{
						kidsInABoat++;
						//I am either the second kid in the boat, or no other kids are left. 
						pilot = true;
						Lib.debug(dbgBoat, KThread.currentThread().getName() + " sees " +
									childrenOnOahu + " kids on Ohau");
						if(childrenOnOahu > 1){
							withPassenger = true;
						}
						Lib.debug(dbgBoat, KThread.currentThread().getName() + " will be the pilot");
					}
					
					Lib.debug(dbgBoat, KThread.currentThread().getName() + " getting ready to ride");
					boatLock.acquire();
					if(pilot){
						childrenOnOahu--;
						Lib.debug(dbgBoat, KThread.currentThread().getName() + " decremented kid count to " + childrenOnOahu);
						adultsLeft = adultsOnOahu;
						kidsLeft = childrenOnOahu;
						bg.ChildRowToMolokai();
						myLocation = boatLocation = MOLOKAI;
						childrenOnMolokai++;
						Lib.debug(dbgBoat, KThread.currentThread().getName() + " waking the passenger");
						childCanTravelOnOhau.wake();
						canTravelOnMolokai.wake();
						myTurnLock.release();
						if(withPassenger){
							//wait until the passenger is done riding too
							Lib.debug(dbgBoat, KThread.currentThread().getName() + " waiting for passenger");
							rideWithPassenger.sleep();
							if(kidsLeft == 1 && adultsLeft == 0){
								Lib.debug(dbgBoat, KThread.currentThread().getName() + " sees " +
											kidsLeft + " kidsleft and " + adultsLeft + " adults left" + 
											" and is done");
								iamdone = true;
							}

						}else{
							if(kidsLeft == 0 && adultsLeft == 0){
								iamdone = true;
							}
						}
						kidsInABoat--;
						boatLock.release();
						
					}else{
						//I was just a passenger in the boat. 
						childrenOnOahu--;
						Lib.debug(dbgBoat, KThread.currentThread().getName() + " decremented kid count to " + childrenOnOahu);
						kidsInABoat--;
						adultsLeft = adultsOnOahu;
						kidsLeft = childrenOnOahu;
						bg.ChildRideToMolokai();
						myLocation = MOLOKAI;
												
						//if I am a passenger, I am done if there is another kid on Molokai
						//Let the pilot go back. 
						if(childrenOnMolokai > 1){
							iamdone = true;
						}else{
							childrenOnMolokai++;
							
						}
						rideWithPassenger.wake();
						myTurnLock.release();
						boatLock.release();
						if(!iamdone){
							spareKidLock.acquire();
							//we need this spare kid to go back if there is just one
							//lone adult left on the Oahu. So just wait for that
							if(adultsLeft > 0){
								Lib.debug(dbgBoat, KThread.currentThread().getName() + " waiting for action");
								waitForAction.sleep();
								kidOnMolokai = false;
								myTurnLock.acquire();
								kidsCanWait.wake();
								myTurnLock.release();
							}else{
								
								iamdone = true;
							}
							spareKidLock.release();
							
						}
					}
					
					Lib.debug(dbgBoat, KThread.currentThread().getName() + " got to Molokai");
															
				}else{
					//on Molokai. We do not need to wait for a second kid here. 
					Lib.debug(dbgBoat, KThread.currentThread().getName() + " getting ready to travel on Ohau ");
					if(!boatLocation.equals(MOLOKAI)){
						canTravelOnMolokai.sleep();
					}
					Lib.debug(dbgBoat, KThread.currentThread().getName() + " got the boat ");
					boatLock.acquire();
					bg.ChildRowToOahu();
					childrenOnMolokai--;
					childrenOnOahu++;
					Lib.debug(dbgBoat, KThread.currentThread().getName() + " upped up kid count to " + childrenOnOahu);
					myLocation = boatLocation = OHAU;
					if(adultsWaiting > 0){
						adultCanTravelOnOhau.wake();
					}else{
						canTravelonOhau.wake();
					}
					if(childrenOnMolokai > 0){
						kidOnMolokai = true;
						kidsCanWait.wake();
					}
					
					myTurnLock.release();
					boatLock.release();
					
				}
				ThreadedKernel.alarm.waitUntil(500);	
			 pilot = false;
			 withPassenger = false;
			 
			}while(!iamdone);
			
			if(adultsOnOahu == 0 && childrenOnOahu == 0){
				comm1.speak(0);
			}
		}
    	
    }
    
    private static class Adult extends Traveller implements Runnable{
    	
    	public Adult(){
    		counterLock.acquire();
			adultsOnOahu++;
			counterLock.release();
    	}

		@Override
		public void run() {
			String myLocation = OHAU;
			boolean iamdone = false;
			
			do{
				if(myLocation.equals(OHAU)){
					myTurnLock.acquire();
					if(!boatLocation.equals(OHAU)){
						canTravelonOhau.sleep();
					}
					if(kidsInABoat > 0){
						//kid is in a boat, wait in a special adult line. 
						//the kid will try to wake up the thread from this list first.
						adultsWaiting++;
						adultCanTravelOnOhau.sleep();
					}
					adultsWaiting--;
					boatLock.acquire();
					adultsOnOahu--;
					bg.AdultRowToMolokai();
					myLocation = boatLocation = MOLOKAI;
					canTravelOnMolokai.wake();
					myTurnLock.release();
					Lib.debug(dbgBoat, KThread.currentThread().getName() + 
							" sees" + childrenOnMolokai + " kids on Molokai and "
							+ childrenOnOahu + " children " + adultsOnOahu + " adults on Ohau");
					if(childrenOnMolokai > 0 || (adultsOnOahu == 0 && childrenOnOahu == 0)){
						//if there are kids on Molokai, we can finish.
						//A kid should row the boat back, since the adult cannot 
						//take passengers. The adult only needs to go back if there
						// are no kids to row the boat back. 
						
						iamdone = true;		
						//if(adultsOnOahu == 0){
							spareKidLock.acquire();
							Lib.debug(dbgBoat, KThread.currentThread().getName() + " waking up spare kid");
							waitForAction.wake();
							spareKidLock.release();
						//}
						Lib.debug(dbgBoat, KThread.currentThread().getName() + 
								" set Iamdone to " + iamdone);
						
					}
					boatLock.release();  //wait to release this lock until the children
										//count is checked, in case a kid grabs a boat
										//and runs back to Ohau
					
				}else{
					//on Molokai. If we are here and not done, then it better means
					//we are going right back. But check if the boat is here just in case
					myTurnLock.acquire();
					if(!boatLocation.equals(MOLOKAI)){
						canTravelOnMolokai.sleep();
					}
										
					boatLock.acquire();
					bg.AdultRowToOahu();
					
					myLocation = boatLocation = OHAU;
					adultsOnOahu++;
					canTravelonOhau.wake();
					
					boatLock.release();
					myTurnLock.release();
					//now sleep a bit, so we do not jump in line if front
					//of everybody again.
					ThreadedKernel.alarm.waitUntil(500);
				}
				
			}while(!iamdone);
			if(adultsOnOahu == 0 && childrenOnOahu == 0){
				comm1.speak(0);
			}
			
		}
    	
    }
    
    
}
